/*	--- 集成JPA --- 

上一节我们讲了【在Spring中, 集成Hibernate】。

[Hibernate]是[第一个被广泛使用的ORM框架]，但是很多小伙伴还听说过
	JPA：Java Persistence API (Java持久性API)
这又是啥？

在讨论[JPA]之前，我们要注意到[JavaEE]早在[1999年]就发布了，并且有[Servlet、JMS]等诸多标准。

和其他平台不同，[Java世界早期]非常热衷于[标准先行]，各家跟进：大家先坐下来把接口定了，然后，各自回家干活去实现接口

这样，[用户]就可以在[不同的厂家]提供的产品进行选择，还可以[随意切换]，因为用户编写代码的时候只需要[引用接口]，并[不需要 引用具体的底层实现]（想想JDBC）。

[JPA]就是[JavaEE的一个ORM标准]，[它的实现]其实和[Hibernate]没啥本质区别，但是[用户]如果【使用JPA】，那么【引用】的就是【javax.persistence这个“标准”包】，而不是【org.hibernate这样的第三方包】。

因为[JPA]只是[接口]，所以，还需要选择[一个实现产品]，跟[JDBC接口]和[MySQL驱动]一个道理。

我们【使用JPA时】也【完全可以选择Hibernate】作为【底层实现】，但也可以【选择其它的JPA提供方】，比如: EclipseLink。

【Spring】内置了[JPA的集成]，并支持【选择 Hibernate/EclipseLink]作为[实现]。

-----------------------------

这里我们仍然以[主流的Hibernate]作为[JPA实现]为例子，演示[JPA的基本用法]。


和使用Hibernate一样，我们只需要引入如下依赖：

	· org.springframework:spring-context:5.2.0.RELEASE
	· org.springframework:spring-orm:5.2.0.RELEASE
	· javax.annotation:javax.annotation-api:1.3.2
	· org.hibernate:hibernate-core:5.4.2.Final
	· com.zaxxer:HikariCP:3.4.2
	· org.hsqldb:hsqldb:2.5.0


然后，在[AppConfig]中, 启用[声明式事务管理]，【创建DataSource】： */
@Configuration
@ComponentScan 
@EnableTransactionManagement 
@PropertySource("jdbc.properties") 
public class AppConfig {
	@Bean 
	DataSource createDataSource() {
		//...
	}
}

/*
【使用Hibernate】时，我们需要【创建一个 LocalSessionFactoryBean】，并【让它再自动创建一个 SessionFactory】。

-------------------

【使用JPA】也是类似的，我们需要创建一个
	[LocalContainerEntityManagerFactoryBean]
并让它【再自动创建一个】
	[EntityManagerFactory] ： */
@Configuration
@ComponentScan 
@EnableTransactionManagement 
@PropertySource("jdbc.properties") 
public class AppConfig {
	@Bean 
	DataSource createDataSource() {
		//...
	}

	@Bean 
	LocalContainerEntityManagerFactoryBean createEntityManagerFactory(@Auto wired DataSource dataSource) {

		var entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();

	// 设置DataSource
		entityManagerFactoryBean.setDataSource(dataSource);
	// 扫描指定的package, 获取所有entity class
		entityManagerFactoryBean.setPackagesToScan("com.itranswarp.learnjava.entity");

		// 指定JPA的提供商 -- Hibernate
		JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
			// vendor n. 小贩,摊贩
		entityManagerFactoryBean.setJpaVendorAdapter(vendorAdapter);

	// 设定特定提供商 = 自己的配置
		var props = new Properties();

		props.setProperty("hibernate.hbm2ddl.auto", "update");
		props.setProperty("hibernate.dialect", "org.hibernate.dialect.HSQLDialect");
		props.setProperty("hibernate.show_sql", "true");

		entityManagerFactoryBean.setJpaProperties(props);

		return entityManagerFactoryBean;
	}

	@Bean 
	PlatformTransactionManager createTxManager(@Autowired EntityManagerFactory entityManagerFactory) {
		return new JpaTransactionManager(entityManagerFactory);
	}
}

/*
观察上述代码，除了【需要注入DataSource】和【设定自动扫描的package】外，还需要【指定JPA的提供商】

-------------------

这里【使用Spring提供的一个 HibernateJpaVendorAdapter】

然后，针对【Hibernate】配置【自己需要的配置】，以【Properties的形式】注入。

最后，我们还需要【实例化一个 JpaTransactionManager】，以实现【声明式事务】： */
@Bean 
PlatformTransactionManager createTxManager(@Autowired EntityManagerFactory entityManagerFactory) {
	return new JpaTransactionManager(entityManagerFactory);
}


/*
这样，我们就完成了JPA的全部初始化工作。

有些童鞋可能从网上搜索得知JPA需要[persistence.xml配置文件]，以及[复杂的orm.xml文件]。

这里我们负责地告诉大家，使用[Spring+Hibernate]作为[JPA实现]，无需[任何配置文件]。

--------------------

[所有Entity Bean的配置]和[上一节]完全相同，全部采用[Annotation标注]。

我们现在只需关心[具体的业务类]如何通过[JPA接口]操作[数据库]。

还是以[UserService]为例，除了【标注@Component和@Transactional】外，我们需要【注入一个EntityManager】

但是【不要使用Autowired】，而是【@PersistenceContext】： */
@Component 
@Transactional 
public class UserService {
	@PersistenceContext 
	EntityManager em;
}

/*
我们回顾一下【JDBC、Hibernate和JPA提供的接口】

实际上，【它们的关系】如下：

JDBC			Hibernate			JPA
---------------------------------------------
DataSource		SessionFactory	EntityManagerFactory

Connection		Session			EntityManager
----------------------------------------------


SessionFactory 和 EntityManagerFactory == DataSource
Session 和 EntityManager == Connection

【每次】需要【访问数据库】的时候，需要获取[新的 Session/EntityManager]，【用完后】再【关闭】。

但是，注意到[UserService]注入的[不是 EntityManagerFactory]，[而是 EntityManager]，并且[标注了 @PersistenceContext]。

难道[使用JPA]可以允许[多线程]操作[同一个EntityManager]？

实际上【这里注入的】并不是【真正的EntityManager】，而是【一个EntityManager的代理类】，相当于： */
public class EntityManagerProxy implements EntityManager {
	private EntityManagerFactory emf;
}

/*
[Spring]遇到标注了[@PersistenceContext的EntityManager]会【自动注入代理】，【该代理】会在【必要的时候】自动打开【EntityManager】。

换句话说，[多线程]引用的【EntityManager】虽然是【同一个代理类】，但该[代理类内部]针对【不同线程】会【创建不同的EntityManager实例】。

-------------------

简单总结一下，标注了 
	@PersistenceContext 
的 EntityManager 可以被【多线程】安全地【共享】。 

因此，在【UserService的每个业务方法】里，【直接使用EntityManager】就很方便。

以【主键查询】为例： */
public User getUserById(long id) {

	User user = this.em.find(User.class, id);
		// this.EntityManager.find(User.class, id);

	if (user == null) {
		throw new RuntimeException("User not found by id: " + id);
	}

	return user;
}

/*
【JPA】同样支持【Criteria查询】，比如【我们需要的查询】如下： 

	Select * From user Where email = ?

使用【Criteria查询】的代码如下： */
public User fetchUserByEmail(String email) {
	// CriteriaBuilder 
	var cb = em.getCriteriaBuilder();

	CriteriaQuery<User> q = cb.createQuery(User.class);

	Root<User> r = q.from(User.class);
	q.where(cb.equal(
		r.get("email"), 
		cb.parameter(String.class, "e")
		)
	);

	TypedQuery<User> query = em.createQuery(q);

	// 绑定参数
	query.setParameter("e", email);

	// 执行查询
	List<User> list = query.getResultList();

	return list.isEmpty() ? null : list.get(0);
}

/*
[一个简单的查询]用【Criteria】写出来就像【上面那样复杂】，太恐怖了

如果【条件】多加几个，【这种写法】谁读得懂？

所以，【正常人】还是建议写【JPQL查询】，【它的语法】和【HQL】基本差不多： */
public User getUserByEmail(String email) {
	// JPQL查询
	TypedQuery<User> query = em.createQuery(
		"Select u From User u Where u.email = :e",
		User.class
	);

	query.setParameter("e", email);

	List<User> list = query.getResultList();

	if (list.isEmpty()) {
		throw new RuntimeException("User not found by email.");
	}

	return list.get(0);
}

/*
同样的，[JPA]也支持[NamedQuery]，即先给[查询]起个名字，再[按名字]创建[查询]： */
public User login(String email, String password) {

	TypedQuery<User> query = em.createNamedQuery("login", User.class);

	query.setParameter("e", email);
	query.setParameter("p", password);

	List<User> list = query.getResultList();

	return list.isEmpty() ? null : list.get(0);
}

/*
同样的，[JPA]也支持[NamedQuery]，即先给[查询]起个[名字]，再【按名字创建查询】： */
public User login(String email, String password) {
	
	TypedQuery<User> query = em.createNamedQuery("login", User.class);
		// EntityManager.createNamedQuery()
	
	query.setParameter("e", email);
	query.setParameter("p", password);

	List<User> list = query.getResultList();

	return list.isEmpty() ? null : list.get(0);
}

/*
NamedQuery通过注解标注在User类上，它的定义和上一节的User类一样： */
@NamedQueries(
	@NamedQuery(
		name = "login", 
		query = "Select u From User u Where u.email =: e AND u.password =: p"
	)
)
@Entity 
public class User {
	//...
}

/*
对数据库进行增删改的操作，可以分别使用
	persist() v. 顽强地坚持;执著地做;维持;保持;持续存在
	remove()
	merge()   v. (使)合并，结合，并入;相融;融入;渐渐消失在某物中

参数均为[Entity Bean本身]，使用非常简单，这里不再多述。


======================================================


#	练习

使用JPA	*/


/*=====================================================

#	----- 集成JPA の 小结 ----- 

1. 在[Spring]中【集成JPA】要选择【一个实现】，可以选择[Hibernate/EclipseLink]；

2. 使用[JPA]与[Hibernate]类似，但【注入的核心资源】是
	带有 
	@PersistenceContext注解 的 [EntityManager代理类]


-------------------------

汇总:  */
@Configuration
@ComponentScan
@EnableTransactionManagement
@PropertySource("jdbc.properties")
public class AppConfig {
    @Bean
    DataSource createDataSource() { 
    	//... 
    }

    @Bean
    LocalContainerEntityManagerFactoryBean createEntityManagerFactory(@Autowired DataSource dataSource) {

        var entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();

        // 设置DataSource:
        entityManagerFactoryBean.setDataSource(dataSource);

        // 扫描指定的package获取所有entity class:
        entityManagerFactoryBean.setPackagesToScan("com.itranswarp.learnjava.entity");

        // 指定JPA的提供商是 Hibernate:
        JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
        entityManagerFactoryBean.setJpaVendorAdapter(vendorAdapter);

        // 设定特定提供商自己的配置:
        var props = new Properties();

        props.setProperty("hibernate.hbm2ddl.auto", "update");
        props.setProperty("hibernate.dialect", "org.hibernate.dialect.HSQLDialect");
        props.setProperty("hibernate.show_sql", "true");

        entityManagerFactoryBean.setJpaProperties(props);

        return entityManagerFactoryBean;
    }

    @Bean
    PlatformTransactionManager createTxManager(@Autowired EntityManagerFactory entityManagerFactory) {
        return new JpaTransactionManager(entityManagerFactory);
    }
}


@NamedQueries(
    @NamedQuery(
        name = "login",
        query = "SELECT u FROM User u WHERE u.email=:e AND u.password=:p"
    )
)
@Entity
public class User {
    private Long id;
	private String email;
	private String password;
	private String name;
	private Long createdAt;

// getters and setters
	//...
}


@Component
@Transactional
public class UserService {

    @PersistenceContext // 注入的是[实体管理器]，执行[持久化操作]
    EntityManager em;
    	// @PersistenceContext 将一般的JavaBean做为了托管状态的Bean，该Bean的任何属性改动都会牵涉到[数据库记录]的改动。

    public User getUserById(long id) {
        User user = this.em.find(User.class, id);

        if (user == null) {
            throw new RuntimeException("User not found by id: " + id);
        }

        return user;
    }

    public User fetchUserByEmail(String email) {
        // CriteriaBuilder:
        var cb = em.getCriteriaBuilder();

        CriteriaQuery<User> q = cb.createQuery(User.class);

        Root<User> r = q.from(User.class);

        q.where(
        	cb.equal(r.get("email"), 
        		cb.parameter(String.class, "e")
        	)
        );

        TypedQuery<User> query = em.createQuery(q);

        // 绑定参数:
        query.setParameter("e", email);

        // 执行查询:
        List<User> list = query.getResultList();

        return list.isEmpty() ? null : list.get(0);
    }

    public User getUserByEmail(String email) {
        // JPQL查询:
        TypedQuery<User> query = em.createQuery(
        	"SELECT u FROM User u WHERE u.email = :e", 
        	User.class
        );
        query.setParameter("e", email);

        List<User> list = query.getResultList();

        if (list.isEmpty()) {
            throw new RuntimeException("User not found by email.");
        }

        return list.get(0);
    }

    public User login(String email, String password) {
        TypedQuery<User> query = em.createNamedQuery(
        	"login", 
        	User.class
        );

        query.setParameter("e", email);
        query.setParameter("p", password);

        List<User> list = query.getResultList();

        return list.isEmpty() ? null : list.get(0);
    }
}


/*
解释：
	@EntityManager 
	EntityManager em;

-------------------------------

注意到UserService注入的【不是EntityManagerFactory】，
而是[EntityManager]，
并且[标注了@PersistenceContext],

实际上
	@EntityManager 
	EntityManager em;

相当于[一个EntityManagerProxy]: */
public class EntityManagerProxy implements EntityManager {
    
    private EntityManagerFactory emf; 
    	// 持有上级接口 EntityManagerFactory

    //...
}












